package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/joho/godotenv"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// signle file, so move field as private
type Todo struct {
	Id        primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
	Completed bool               `json:"completed"`
	Body      string             `json:"body"`
}

var collection *mongo.Collection

func getTodos(c *fiber.Ctx) error {
	var todos []Todo
	cursor, err := collection.Find(context.Background(), bson.M{})
	if err != nil {
		return err
	}
	defer cursor.Close(context.Background())
	for cursor.Next(context.Background()) {
		var td Todo
		if err := cursor.Decode(&td); err != nil {
			return err
		}
		todos = append(todos, td)
	}
	return c.JSON(todos)
}

func createTodo(c *fiber.Ctx) error {
	td := new(Todo)
	if err := c.BodyParser(td); err != nil {
		return err
	}

	if td.Body == "" {
		return c.Status(400).JSON(
			fiber.Map{"error": "Todo content cannot be empty"},
		)
	}

	insertResult, err := collection.InsertOne(context.Background(), td)
	if err != nil {
		return err
	}

	// InsertedID type is interface{}, convert it to primitive.ObjectID
	td.Id = insertResult.InsertedID.(primitive.ObjectID)

	// 201 means created works
	return c.Status(201).JSON(td)
}

func updateTodo(c *fiber.Ctx) error {
	id := c.Params("id") // url push id as param
	objetcID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{
			"error": "Invalid provided todo id, update failed",
		})
	}

	filter := bson.M{"_id": objetcID}
	update := bson.M{"$set": bson.M{"completed": true}}

	_, err = collection.UpdateOne(context.Background(), filter, update)
	if err != nil {
		return err
	}

	return c.Status(200).JSON(fiber.Map{
		"success": true,
	})
}

func deleteTodo(c *fiber.Ctx) error {
	id := c.Params("id")
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return c.Status(400).JSON(fiber.Map{
			"error": "Invalid provided todo id, delete failed",
		})
	}

	filter := bson.M{"_id": objectID}
	_, err = collection.DeleteOne(context.Background(), filter)
	if err != nil {
		return err
	}

	return c.Status(200).JSON(fiber.Map{
		"success": true,
	})
}

func main() {
	// check .env WORKSPACE_ENV
	if os.Getenv("WORKSPACE_ENV") != "production" {
		err := godotenv.Load(".env")
		if err != nil {
			log.Fatal("Error Loading .env file when development workspace env: ", err)
		}
	}
	// check MONGODB_URI env var is set ?
	MONGODB_URI := os.Getenv("MONGODB_URI")
	dbclient_options := options.Client().ApplyURI(MONGODB_URI)
	dbclient, err := mongo.Connect(context.Background(), dbclient_options)

	if err != nil {
		log.Fatal("Error connecting to mongodb: ", err)
	}
	defer dbclient.Disconnect(context.Background())

	err = dbclient.Ping(context.Background(), nil)
	if err != nil {
		log.Fatal("Error ping to mongodb: ", err)
	}

	fmt.Println("[INFO]: Connected to remote mongodb works")
	// check USE_DB env var is set?
	collection = dbclient.Database(os.Getenv("USE_DB")).Collection("todos")

	svr := fiber.New()

	svr.Use(cors.New(cors.Config{
		// TODO: config here to enable frontend fetch backend api
		AllowOrigins: "http://localhost:5173",
		AllowHeaders: "Origin,Content-Type,Accept",
	}))

	svr.Get("/api/todos", getTodos)
	svr.Post("/api/todos", createTodo)
	svr.Patch("/api/todos/:id", updateTodo)
	svr.Delete("/api/todos/:id", deleteTodo)

	// check PORT env var is set ?
	port := os.Getenv("PORT")
	if port == "" {
		port = "6969"
	}

	if os.Getenv("WORKSPACE_ENV") == "production" {
		// TODO: move as env var
		svr.Static("/", "./frontend/dist")
	}

	log.Fatal(svr.Listen("0.0.0.0:" + port))
}
